#ifndef CODE_H
#define CODE_H

#include "visitor.h"
#include <map>
#include <string>
#include <sstream>
#include <iostream>
#include <vector>

class Scope;


/// @brief primitive opcode base
class Opcode{
protected:
    /// the operand number
    int   operandNum;

    /// debug purpose
    std::ostringstream info;
public:
    Opcode(){};
    int getOperandNum(){return operandNum;};
    void print() const{printTo(std::cout);std::cout<<std::endl;};
    void printTo(std::ostream &s) const{s<<info.str();};
    const std::string getPrintInfo(){return std::string(info.str());};

    // is this opcode the end of a basicblock i.e. branch to other basicblock
    virtual int isTerminal() const{return 0;};
};

class BinaryOpcode : public Opcode{
public:
    int dst,lhs,rhs;
    BinaryOpcode(int dest , int l , int r):dst(dest),lhs(l),rhs(r){};
};

class AddOp : public BinaryOpcode{
public:
    AddOp(int rst,int l,int r):BinaryOpcode(rst,l,r){
        operandNum = 3;
        info << "AddOp " << dst << ":" << lhs << ":" << rhs ;
    };
};

class SubOp : public BinaryOpcode{
public:
    SubOp(int rst,int l,int r):BinaryOpcode(rst,l,r){
        operandNum = 3;
        info << "SubOp " << dst << ":" << lhs << ":" << rhs ;
    };
};

class MulOp : public BinaryOpcode{
public:
    MulOp(int rst,int l,int r):BinaryOpcode(rst,l,r){
        operandNum = 3;
        info << "MulOp " << dst << ":" << lhs << ":" << rhs ;
    };
};

class DivOp : public BinaryOpcode{
public:
    DivOp(int rst,int l,int r):BinaryOpcode(rst,l,r){
        operandNum = 3;
        info << "DivOp " << dst << ":" << lhs << ":" << rhs ;
    };
};

class MovOp : public Opcode{
public:
    int src,dst;
    MovOp(int d,int s):src(s),dst(d){
        operandNum = 2;
        info << "MovOp  " << dst << " <= " << src ;
    };
};

/// load from [offset] to dst
class SpillLoad : public Opcode
{
public:
    int dst,offset;
    SpillLoad(int d,int s):dst(d),offset(s)
    {
        operandNum = 2;
        info << "SpillLoad "<<dst << " <= offset:" << offset;
    };
};

/// store from src to [offset]
class SpillStore : public Opcode
{
public:
    int offset,src;
    SpillStore(int d,int s):offset(d),src(s)
    {
        operandNum = 2;
        info << "SpillStore "<<src  << " => offset:" << offset;
    };
};


class GetLocalOp : public Opcode{
public:
    int dst,index;
    GetLocalOp(int d,int i):dst(d),index(i){
        operandNum = 2;
        info << "GetLocalOp " << dst << " index:" << index ;
    };
};

class SetLocalOp : public Opcode{
public:
    int dst,index;
    SetLocalOp(int d,int i):dst(d),index(i){
        operandNum = 2;
        info << "SetLocalOp " << dst << " index:" << index;
    };
};

class GetGlobalOp : public Opcode{
public:
    int dst,index;
    GetGlobalOp(int d,int i):dst(d),index(i){
        operandNum = 2;
        info << "GetGlobalOp " << dst << " index:" << index;
    }
};

class SetGlobalOp : public Opcode{
public:
    int dst,index;
    SetGlobalOp(int d,int i):dst(d),index(i){
        operandNum = 2;
        info << "SetGlobalOp " << dst << " index:" << index;
    };
};

class CondGotoOp : public Opcode{
public:
    /// the condition register
    int cond;

    /// the labels(pc) jump to
    int trueLabel,falseLabel;
    CondGotoOp(int c,int t,int f):cond(c),trueLabel(t),falseLabel(f){
        operandNum = 3;
        info << "CondGotoOp Reg:" << cond << " trueLabel[pc]:" << trueLabel
                << " falseLabel[pc]:" << falseLabel;
    };

    virtual int isTerminal() const{return 1;};
};

class GotoOp : public Opcode{
public:
    /// the destination(pc) jump to
    int dstLabel;
    GotoOp(int d):dstLabel(d){
        operandNum = 1;
        info << "GotoOp to:" << dstLabel;
    };

    virtual int isTerminal() const{return 1;};
};

class LoadConstOp : public Opcode{
public:
    /// the second operand is a constant index of the pool
    int dst,constIndex;
    LoadConstOp(int d,int v):dst(d),constIndex(v){
        operandNum = 2;
        info << "LoadConstOp " << dst << " <= const:" << constIndex;
    };
};

class PushOp : public Opcode{
public:
    /// the index of the register which is pushed to stack
    int index;
    PushOp(int i):index(i){
        operandNum = 1;
        info << "PushOp " << index;
    };
};

class PopOp : public Opcode{
public:
    /// the index of the register to which is popped from stack
    int index;
    PopOp(int i):index(i){
        operandNum = 1;
        info << "PopOp " << index;
    };
};

class CallOp : public Opcode{
public:
    /// the index of the register which contains the function entry(pc)
    int index;
    CallOp(int i):index(i){
        operandNum = 1;
        info << "CallOp[" << index << "]";
    };
};

class RetOp : public Opcode{
public:
    /// the index of the register which contains the result while returning
    int index;
    RetOp(int i):index(i){
        operandNum = 1;
        info << "RetOp{" << index << "}";
    };
};

class NewOp : public Opcode{
    /// similar with r0 = malloc(size)
    int dst,size;
    NewOp(int d,int s):dst(d),size(s){
        operandNum = 2;
        //info << "NewOp " << dst << "<= " << size;
    };
};


class Context{
public:
    /// the function it is attached to
    FunctionSymbol  *funcSym;

    /// the current scope
    //Scope   *curScope;

    /// next available slot for variable
    int     nextSlot;

    /// next register available
    int nextReg;

    static const int NODEST = -1;

    /// the destinate register that opcode store to
    int dstReg;


    /// the state that map localvar to the allocated register
    std::map<int,int> varToReg;

    /// map register to var index
    int regToVar(int reg);

    /// current context
    static Context *curContext;

    Context(FunctionSymbol *sym);
};


class OpCodeGen : public Visitor{
private:

    /// current pc while generating opcodes
    int pc;

    /// the result of last visit
    int result;
public:
    static const int NOP = 0;
    static const int ADD = NOP + 1;
    static const int SUB = ADD + 1;
    static const int MUL = SUB + 1;
    static const int DIV = MUL + 1;
    static const int MOV = DIV + 1;
    static const int GETLOCAL = MOV + 1;
    static const int GETGLOBAL = GETLOCAL + 1;
    static const int SETLOCAL = GETGLOBAL + 1;
    static const int SETGLOBAL = SETLOCAL + 1;
    static const int COND_GOTO = SETGLOBAL + 1;
    static const int GOTO = COND_GOTO + 1;
    static const int LDCONST = GOTO + 1;
    static const int PUSH = LDCONST + 1;
    static const int POP = PUSH + 1;
    static const int CALL = POP + 1;
    static const int RET = CALL + 1;
    static const int NEW = RET + 1;

    typedef std::map<int,Opcode*>   ops_type;
    OpCodeGen();
    ~OpCodeGen();

    void printInfo();
    const char* getCodeName(int op);


    void visitVariableExpr(VariableExpr& expr);
    void visitConstantExpr(ConstantExpr& expr);
    void visitBinaryExpr(BinaryExpr& expr);

    //void visitVariableDeclare(VariableDeclareStatement& stmt);
    void visitAssignment(AssignmentStatement& stmt);
    void visitParameterList(ParameterListStatement& stmt);
    //void visitVariableList(VariableListStatement& stmt);
    void visitExprList(ExprListStatement& stmt);
    void visitFunctionDef(FunctionDefStatement& stmt);
    void visitFunctionCall(FunctionCallStatement& stmt);

    void visitBlock(Block &blk);
    void visitWhileStatement(WhileStatement &stmt);
    void visitIfStatement(IfStatement &stmt);
};

class FunctionManager{
private:
    static FunctionManager *instance;
    FunctionManager(){};
public:
    std::vector<FunctionSymbol*> functions;
    FunctionSymbol  *curFunction;
    static FunctionManager* getInstance(){
        if(!FunctionManager::instance){
            FunctionManager::instance = new FunctionManager();
        }
        return FunctionManager::instance;
    }

    typedef std::vector<FunctionSymbol*>::iterator iterator;
};

#endif

